import json
import logging
import os
import re
import subprocess
import tempfile
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Tuple

import oss2

from langchain.chains.summarize import load_summarize_chain
from langchain.chat_models import init_chat_model
from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
from langchain_text_splitters import RecursiveCharacterTextSplitter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import make_pipeline
from sklearn.svm import LinearSVC

from tortoise.exceptions import DoesNotExist
from tortoise.transactions import in_transaction

from entity.database.mysql import TestCase, Message, TestSuite, FileRecord, Conversation
from utils.createID_tool import ConversationIdGenerator

model = init_chat_model(
    model="deepseek-v3",
    model_provider="openai",
    api_key="	sk-7cc7662dfc9b454b817b788198d21eee",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    temperature=0.5,
    max_tokens=200
)


async def generateTestCase_with_chat(conversationId: str, userInput: str, fileId: str, method: int):
    """
    用户与AI对话并生成测试用例
    :param userInput: 用户在输入框中输入的内容
    :param fileId: 用户上传的文件id
    :param method: 用户选择哪种方式生成测试用例用例（1为全量生成，2为指定功能点生成）
    :return: AI输出的回答或测试用例
    """
    peopleMessageId = await insert_Message(conversationId, userInput, 0)
    response_data = ""

    if fileId != '1':
        await FileRecord.filter(file_id=fileId).update(message_id=peopleMessageId)
        file_path = await get_file_from_oss_by_file_id(fileId)
        print(f"文件路径为:{file_path}")
        response_data = await generate_test_cases_with_rag(userInput, file_path, method)
    else:
        response_data = await generateTestCase_with_nofile(userInput, method)

    # 1. 解析JSON响应
    try:

        # 2. 检查响应状态
        if response_data.get('code') == 10000 and response_data.get('flag'):
            # 3. 提取testCases数组
            test_cases = response_data['data']['testCases']
            AimessageId = await insert_Message(conversationId, "测试用例如下", 1)
            suiteId = await insert_testsuite(conversationId, AimessageId)
            # 4. 遍历并处理每个测试用例
            for case in test_cases:
                print(f"用例编号: {case['casenum']}")
                print(f"功能点: {case['feature']}")
                print(f"测试场景: {case['scenario']}")
                print(f"前置条件: {case['preconditions']}")
                print(f"测试步骤: {case['steps']}")
                print(f"预期结果: {case['expected']}")
                print("-" * 40)
                await insert_testcase(case['casenum'], case['feature'], case['scenario'], case['preconditions'],
                                      case['steps'], case['expected'], suiteId)
        else:
            print(f"生成测试用例失败: {response_data.get('msg')}")
            await insert_Message(conversationId, response_data.get('msg'), 1)

    except json.JSONDecodeError:
        print("响应不是有效的JSON格式")
    except KeyError as e:
        print(f"响应缺少必要字段: {e}")

    # ===== 新增的标题更新逻辑 =====
    try:
        conversation = await Conversation.get_or_none(conversation_id=conversationId)

        if conversation and conversation.title == "新对话":
            # 使用您实际的标题生成服务
            new_title = await generate_ai_title(userInput)

            async with in_transaction():
                conversation.title = new_title
                await conversation.save()

            print(f"对话标题更新: {conversationId} -> {new_title}")
    except Exception as e:
        print(f"标题更新失败: {e}")
        # ===== 结束新增逻辑 =====
    return response_data


# class TestCaseIntentRecognizer:
#     def __init__(self):
#         # 使用清华镜像源
#         self.tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
#         self.model = AutoModelForSequenceClassification.from_pretrained("bert-base-chinese")
#
#         # 规则引擎配置
#         self.keywords = {
#             "positive": ["测试用例", "test case", "验证", "检查", "功能测试", "场景测试"],
#             "negative": ["你好", "谢谢", "天气", "success", "hi", "hello"]
#         }
#         self.feature_indicators = ["功能", "模块", "登录", "注册", "支付", "接口"]
#
#         # 缓存机制
#         self.cache = {}
#
#     async def _rule_based_check(self, text: str) -> float:
#         """规则引擎给出初步评分（0-1）"""
#         # 快速过滤
#         if len(text.strip()) < 3 or re.match(r'^[\W_]+$', text):
#             return 0.0
#
#         # 否定词检查
#         if any(keyword in text.lower() for keyword in self.keywords["negative"]):
#             return 0.0
#
#         # 关键词匹配
#         score = 0.3 if any(keyword in text for keyword in self.keywords["positive"]) else 0
#         score += 0.2 if any(indicator in text for indicator in self.feature_indicators) else 0
#
#         return min(score, 0.5)  # 规则引擎最高给0.5分
#
#     async def _model_predict(self, text: str) -> float:
#         """模型预测给出评分（0-1）"""
#         inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=128)
#         outputs = self.model(**inputs)
#         probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
#         return probs[0][1].item()  # 返回正例概率
#
#     async def intent_recognition(self, user_input: str) -> Tuple[bool, Dict[str, Any]]:
#         """综合意图识别"""
#         # 缓存检查
#         if user_input in self.cache:
#             return self.cache[user_input]
#
#         # 第一步：规则快速过滤
#         rule_score = await self._rule_based_check(user_input)
#         if rule_score == 0:
#             self.cache[user_input] = (False, {
#                 "code": 20001,
#                 "msg": "请在输入框中输入明确需求或上传需求文档"
#             })
#             return self.cache[user_input]
#
#         # 第二步：模型预测（仅当规则评分>0时触发）
#         model_score = await self._model_predict(user_input)
#         combined_score = rule_score + model_score * 0.5  # 加权综合
#
#         # 决策阈值
#         if combined_score >= 0.6:  # 可调整阈值
#             result = (True, None)
#         else:
#             result = (False, {
#                 "code": 20001,
#                 "msg": "请在输入框中输入明确需求或上传需求文档"
#             })
#
#         self.cache[user_input] = result
#         return result

async def insert_testcase(casenum: str, feature: str, scenario: str, preconditions: str, steps: str, expected: str,
                          suiteId: str):
    # 使用UUID方案
    caseId = ConversationIdGenerator.generate_timestamp_random()
    # 检查ID是否已存在（极低概率冲突处理）
    while await TestCase.exists(case_id=caseId):
        logging.warning(f"ID冲突: {caseId}，重新生成")
        caseId = ConversationIdGenerator.generate_timestamp_random()

    # 创建对话记录
    await TestCase.create(
        case_id=caseId,
        case_num=casenum,
        feature=feature,
        test_scene=scenario,
        preconditions=preconditions,
        step=steps,
        expected=expected,
        created_time=datetime.now(),
        updated_time=datetime.now(),
        suite_id=suiteId
    )


async def insert_Message(conversationId: str, input: str, source: int):
    # 使用UUID方案
    messageId = ConversationIdGenerator.generate_timestamp_random()
    # 检查ID是否已存在（极低概率冲突处理）
    while await Message.exists(message_id=messageId):
        logging.warning(f"ID冲突: {messageId}，重新生成")
        messageId = ConversationIdGenerator.generate_timestamp_random()
    await Message.create(
        message_id=messageId,
        conversation_id=conversationId,
        content=input,
        message_source=source,
        created_time=datetime.now(),
        updated_time=datetime.now()
    )
    return messageId


async def insert_testsuite(conversationId: str, messageId: str):
    # 使用UUID方案
    suiteId = ConversationIdGenerator.generate_timestamp_random()
    # 检查ID是否已存在（极低概率冲突处理）
    while await TestSuite.exists(suite_id=suiteId):
        logging.warning(f"ID冲突: {suiteId}，重新生成")
        suiteId = ConversationIdGenerator.generate_timestamp_random()
    await TestSuite.create(
        suite_id=suiteId,
        conversation_id=conversationId,
        message_id=messageId,
        created_time=datetime.now(),
        updated_time=datetime.now()
    )
    return suiteId


# 获取项目根目录
PROJECT_ROOT = Path(__file__).resolve().parent.parent


async def get_file_from_oss_by_file_id(file_id: str):
    """
    根据文件ID从阿里云OSS获取文件
    :param file_id: 文件ID
    :return: (文件名, 文件内容)
    """
    try:
        # 1. 根据fileId从数据库获取文件信息
        file_record = await FileRecord.get(file_id=file_id)

        oss_bucket = file_record.oss_bucket
        oss_key = file_record.oss_key
        file_name = file_record.file_name

    except DoesNotExist:
        raise ValueError(f"文件ID {file_id} 不存在")

    # 2. 配置阿里云OSS连接
    auth = oss2.Auth(
        "LTAI5t7gXWXyKvbyAG6HgvZH",
        "tQSyMIEU3vKpPkPzwfXAKJXmEAEvUx"
    )

    endpoint = os.getenv('OSS_ENDPOINT', 'https://oss-cn-hangzhou.aliyuncs.com')

    # 3. 创建项目tmp目录（如果不存在）
    tmp_dir = PROJECT_ROOT / 'tmp'
    tmp_dir.mkdir(parents=True, exist_ok=True)

    # 4. 设置本地文件路径
    local_file_path = tmp_dir / file_name

    # 5. 获取文件
    bucket = oss2.Bucket(auth, endpoint, oss_bucket)

    try:
        # 下载文件到项目tmp目录
        bucket.get_object_to_file(oss_key, str(local_file_path))

        return str(local_file_path)

    except oss2.exceptions.NoSuchKey:
        raise FileNotFoundError(f"OSS上不存在该文件: {oss_key}")
    except Exception as e:
        # 如果下载失败，删除可能存在的部分文件
        if local_file_path.exists():
            try:
                os.unlink(str(local_file_path))
            except:
                pass
        raise RuntimeError(f"获取文件失败: {str(e)}")


class IntentClassifier:
    def __init__(self):
        # 训练数据：文本 -> 意图标签 (0=无效, 1=有效)
        self.train_texts = [
            # 无效输入
            "success", "hello", "ok", "开始", "生成测试", "测试", "你好",
            "hi", "测试一下", "开始测试", "生成用例", "用例生成",
            "进行测试", "执行测试", "测试功能",

            # 有效输入
            "用户登录功能测试", "购物车添加商品流程", "支付功能验证",
            "生成登录功能的测试用例", "需要测试用户注册流程",
            "验证商品搜索功能", "测试订单创建流程",
            "为登录功能创建测试用例", "生成用户管理模块的测试用例",
            "测试文件上传功能", "验证支付流程",
            "需要购物车功能的测试用例", "生成搜索功能的测试案例",
            "测试用户资料修改流程", "验证订单取消功能",
            "登录功能测试", "注册流程验证", "支付接口测试"
        ]
        self.train_labels = [
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  # 无效
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1  # 有效
        ]

        self.model = make_pipeline(
            TfidfVectorizer(ngram_range=(1, 2)),  # 使用1-gram和2-gram
            LinearSVC(class_weight='balanced')  # 注意这里使用 LinearSVC，并且参数是 class_weight
        )
        # 训练模型
        self.train()

    def train(self):
        # 训练模型
        self.model.fit(self.train_texts, self.train_labels)

    def predict(self, text):
        # 预测意图
        prediction = self.model.predict([text])
        # 返回预测结果 (0=无效, 1=有效)
        return prediction[0]


# 初始化意图识别器（全局单例）
intent_classifier = IntentClassifier()


async def generateTestCase_with_nofile(userInput: str, method: int):
    # 1. 使用意图识别模型判断用户输入
    is_valid = intent_classifier.predict(userInput)

    if not is_valid:
        return {
            "code": 20000,
            "flag": False,
            "data": None,
            "msg": "无法识别您的需求。请提供具体的功能描述，例如：'测试用户登录功能'"
        }
    # 根据method值确定测试范围描述
    if method == 1:
        scope_desc = "生成覆盖所有功能点的完整测试用例集合（全量测试）"
    elif method == 2:
        scope_desc = "生成用户输入中明确指定的功能点的测试用例（指定功能点测试）"
    else:

        return {
            "code": 20000,
            "flag": False,
            "data": None,
            "msg": "无效的测试方法参数，请使用1（全量）或2（指定功能点）"
        }
    json_example = """
    {{
                "code": 10000,  
                "flag": true,  
                "data": {{  
                    "testCases": [  
                        {{  
                            "feature": "功能点",  
                            "casenum": "TC-001",
                            "scenario": "测试场景",  
                            "preconditions": "前置条件",  
                            "steps": "测试步骤",  
                            "expected": "预期结果"  
                        }}
                    ]  
                }},  
                "msg": "成功生成测试用例"  
    }}
    """
    prompt = f"""
        (C) 上下文：
        你是一个专业的测试用例生成API，根据用户输入的需求生成结构化测试用例。
        如果用户输入不是有效的功能需求描述（如简单的单词、无意义的字符串等），请返回错误响应。

        (O) 目标：
        1. 解析用户输入的功能需求
        2. {scope_desc}
        3. 输出严格符合以下json结构：
            {json_example}

        (S) 风格：
        - 仅输出JSON格式，无任何额外文本（包括任何注释、解释、markdown符号等）    
        - 技术术语准确，描述清晰无歧义
        - 绝对不要使用```json或```包裹JSON
        - 测试步骤和预期结果使用分号分隔多步骤

        (R) 响应规则：
        有效输入（code=10000）：
            - flag=true
            - data.testCases包含≥1个用例
            - msg="成功生成测试用例"

        无效输入（code=20000）：            
            - flag=false
            - data=null
            - msg="无法识别您的需求。请提供具体的功能描述，例如：'测试用户登录功能'"

        用例字段要求：
        - feature: 清晰的功能点名称
        - casenum: 格式为"TC-XXX"的用例编号
        - steps：用分号分隔步骤（例："步骤1;步骤2"）
        - expected：用分号分隔结果（例："结果1;结果2"）

         (V) 输入有效性评估标准：
        - 输入长度应大于5个字符
        - 输入应包含功能描述或测试场景
        - 如果输入是简单的单词（如"success"、"hello"等）或通用指令（如"开始测试"），则无效
        - 如果输入包含具体功能点（如"登录"、"支付"）或操作（如"用户添加商品到购物车"），则有效
        - 即使输入包含"生成"、"测试用例"等词，只要同时描述具体功能，也是有效输入

        (M) 测试方法说明：
        - 当method=1（全量测试）时，生成覆盖所有功能点的测试用例
        - 当method=2（指定功能点测试）时，只生成用户明确提到的功能点的测试用例

        注意：
        1. 绝对不要输出任何非JSON内容，包括markdown格式
        2.仅输出JSON格式，无任何额外文本（包括任何注释、解释、markdown符号等）
        3.绝对不要使用```json或```包裹JSON 
        2. 确保每个字段都存在且类型正确
        3. 如果用户输入无法识别为有效的功能需求，返回错误响应
    """

    if method == 2:
        userInput = f"【指定功能点测试】{userInput}"
    else:
        userInput = f"【全量测试】{userInput}"

    prompt_template = ChatPromptTemplate.from_messages(
        [
            ("system", prompt),
            ("human", f"用户需求: {userInput}")
        ]
    )

    try:
        message = prompt_template.invoke({"userInput": userInput})
        response = model.invoke(input=message)

        # 确保返回结构相同
        try:
            # 尝试解析模型返回的JSON
            response_data = json.loads(response.content)

            # 确保数据结构与返回的一致
            if "code" not in response_data:
                response_data["code"] = 10000 if response_data.get("flag", False) else 20000

            if "flag" not in response_data:
                response_data["flag"] = response_data.get("code", 20000) == 10000

            if "msg" not in response_data:
                response_data["msg"] = "成功生成测试用例" if response_data["flag"] else "生成测试用例失败"

            # 确保data字段存在且包含testCases
            if "data" not in response_data:
                response_data["data"] = {"testCases": []}

            return response_data

        except json.JSONDecodeError:
            # 如果解析失败，返回标准错误结构
            return {
                "code": 20000,
                "flag": False,
                "data": None,
                "msg": "测试用例生成结果解析失败"
            }

    except Exception as e:
        # 返回相同的错误结构
        return {
            "code": 20000,
            "flag": False,
            "data": None,
            "msg": f"生成测试用例时出错: {str(e)}"
        }


async def generate_test_cases_with_rag(
        user_input: str,
        file_path: str,
        method: int
) -> Dict[str, Any]:
    """
    使用RAG技术结合文件内容和用户输入生成测试用例
    :param user_input: 用户输入的测试需求
    :param file_path: 下载文件的路径
    :param method: 生成方式 (1=全量生成, 2=指定功能点生成)
    :return: 符合规范的测试用例JSON响应
    """
    try:
        if method == 2:  # 指定功能点生成
            is_valid = await validate_feature_input(user_input)
            if not is_valid:
                return {
                    "code": 20000,
                    "flag": False,
                    "data": None,
                    "msg": "请明确指定要测试的功能点（例如：'生成登录功能的测试用例'）"
                }
        # 1. 读取文件内容
        file_content = read_file_content(file_path)
        if not file_content:
            return error_response("文件内容为空")

        # 2. 检查文档内容相关性
        is_relevant = await check_content_relevance(user_input, file_content)
        if not is_relevant:
            return {
                "code": 20000,
                "flag": False,
                "data": None,
                "msg": "文档内容与测试需求无关，无法生成测试用例"
            }

        # 2. 创建文本分割器
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=200,
            length_function=len,
        )

        # 3. 分割文档
        docs = [Document(page_content=file_content)]
        split_docs = text_splitter.split_documents(docs)

        # 4. 根据 method 选择不同的提示词模板
        if method == 1:  # 全量测试用例生成
            map_prompt_template = """
            基于以下文档片段和用户需求，识别相关功能点并生成测试用例的初步想法：

            文档片段：
            {text}

            用户需求：
            {user_input}

            请专注于识别所有可能的测试点，不需要生成完整测试用例。
            """

            reduce_prompt_template = """
            综合以下所有文档片段的分析结果，生成完整的结构化测试用例集合：

            {text}

            用户需求：
            {user_input}

            输出要求：
            1. 生成文档中所有主要功能的测试用例
            2. 严格使用以下JSON格式输出：
            {{
                "code": 10000,  
                "flag": true,  
                "data": {{  
                    "testCases": [  
                        {{  
                            "feature": "功能点",  
                            "casenum": "TC-001",
                            "scenario": "测试场景",  
                            "preconditions": "前置条件",  
                            "steps": "测试步骤",  
                            "expected": "预期结果"  
                        }}
                    ]  
                }},  
                "msg": "成功生成全量测试用例"  
            }}

            3. 测试步骤和预期结果用分号分隔多步骤
            4. 确保每个字段都存在
            5. 不要包含任何额外文本或JSON标记
            """

            msg_success = "成功生成全量测试用例"

        else:  # method == 2，指定功能点测试用例生成
            map_prompt_template = """
            基于以下文档片段和用户需求，识别与特定功能点相关的测试用例想法：

            文档片段：
            {text}

            用户需求：
            {user_input}

            请专注于识别用户指定的功能点相关的测试点。
            """

            reduce_prompt_template = """
            综合以下所有文档片段的分析结果，生成针对特定功能点的结构化测试用例：

            {text}

            用户需求：
            {user_input}

            重要提示：只生成用户指定的功能点的测试用例，不要生成其他功能的测试用例。

            输出要求：
            1. 只生成用户需求中明确指定的功能点的测试用例
            2. 严格使用以下JSON格式输出：
            {{
                "code": 10000,  
                "flag": true,  
                "data": {{  
                    "testCases": [  
                        {{  
                            "feature": "功能点",  
                            "casenum": "TC-001",
                            "scenario": "测试场景",  
                            "preconditions": "前置条件",  
                            "steps": "测试步骤",  
                            "expected": "预期结果"  
                        }}
                    ]  
                }},  
                "msg": "成功生成指定功能点测试用例"  
            }}

            3. 测试步骤和预期结果用分号分隔多步骤
            4. 确保每个字段都存在
            5. 不要包含任何额外文本或JSON标记
            """

            msg_success = "成功生成指定功能点测试用例"

        # 5. 创建提示词模板
        map_prompt = PromptTemplate(
            template=map_prompt_template,
            input_variables=["text", "user_input"]
        )

        reduce_prompt = PromptTemplate(
            template=reduce_prompt_template,
            input_variables=["text", "user_input"]
        )

        # 6. 创建Map-Reduce链
        chain = load_summarize_chain(
            model,
            chain_type="map_reduce",
            map_prompt=map_prompt,
            combine_prompt=reduce_prompt,
            verbose=True
        )

        # 7. 执行链
        result = chain.run(
            input_documents=split_docs,
            user_input=user_input
        )

        # 8. 解析结果
        try:
            response = json.loads(result)
            # 更新成功消息
            if response.get("code") == 10000:
                response["msg"] = msg_success
            return response
        except json.JSONDecodeError:
            return parse_and_fix_json(result)

    except Exception as e:
        print(f"生成测试用例失败: {str(e)}")
        return error_response(f"生成测试用例失败: {str(e)}")


async def validate_feature_input(user_input: str) -> bool:
    """
    验证指定功能点生成方式的用户输入是否有效
    返回 True 表示有效，False 表示无效
    """
    # 1. 检查输入是否为空
    if not user_input or user_input.strip() == "":
        return False

    # 2. 检查输入是否包含常见无效内容
    invalid_phrases = [
        "你好", "hello", "hi", "您好",
        "今天天气", "天气不错", "天气真好",
        "这个鱼好大", "鱼好大", "success",
        "谢谢", "thank you", "请帮忙",
        "怎么样", "如何", "?"
    ]

    # 转换为小写进行比较
    lower_input = user_input.lower()
    for phrase in invalid_phrases:
        if phrase in lower_input:
            return False

    # 3. 使用LLM进一步验证输入有效性
    return await analyze_feature_input(user_input)


async def analyze_feature_input(user_input: str) -> bool:
    """
    使用LLM分析用户输入是否包含有效的功能点描述
    """
    try:
        # 创建输入有效性检查提示词
        prompt_template = """
        你是一个测试分析师，负责评估用户输入是否包含有效的功能点描述。
        请根据以下用户输入进行判断：

        用户输入：
        {user_input}

        判断标准：
        1. 输入是否描述了一个具体的软件功能或模块
        2. 输入是否包含可测试的功能点名称
        3. 输入是否明确请求生成特定功能的测试用例

        有效输入示例：
        - "生成登录功能的测试用例"
        - "为支付模块创建测试用例"
        - "测试用户注册流程"

        无效输入示例：
        - "你好"
        - "今天天气真不错"
        - "这个鱼好大啊"
        - "success"
        - "hello"

        请只回答一个字："是"或"否"，不要添加任何解释。
        """

        # 创建提示词
        prompt = PromptTemplate(
            template=prompt_template,
            input_variables=["user_input"]
        )

        # 执行检查
        chain = prompt | model | StrOutputParser()
        result = await chain.ainvoke({"user_input": user_input})

        # 解析结果
        return "是" in result or "有效" in result or "yes" in result.lower()

    except Exception as e:
        print(f"输入有效性检查失败: {str(e)}")
        # 出错时默认认为无效
        return False


async def check_content_relevance(user_input: str, file_content: str) -> bool:
    """
    检查文档内容是否与测试需求相关
    返回 True 表示相关，False 表示无关
    """
    try:
        # 1. 创建相关性检查提示词
        prompt_template = """
        你是一个测试分析师，负责评估文档内容是否与测试需求相关。
        请根据以下文档内容和用户需求进行判断：

        文档内容摘要：
        {content_summary}

        用户需求：
        {user_input}

        判断标准：
        1. 文档是否包含软件需求、功能描述或系统设计相关内容
        2. 文档是否描述系统行为、用户交互或业务流程
        3. 文档是否包含可测试的功能点或业务规则

        请回答：
        该文档内容是否与软件测试需求相关？只回答"是"或"否"。
        """

        # 2. 生成内容摘要（避免直接使用全部内容）
        content_summary = await generate_content_summary(file_content)

        # 3. 创建提示词
        prompt = PromptTemplate(
            template=prompt_template,
            input_variables=["content_summary", "user_input"]
        )

        # 4. 执行相关性检查
        chain = prompt | model | StrOutputParser()
        result = await chain.ainvoke({
            "content_summary": content_summary,
            "user_input": user_input
        })

        # 5. 解析结果
        return "是" in result or "相关" in result or "yes" in result.lower()

    except Exception as e:
        print(f"相关性检查失败: {str(e)}")
        # 出错时默认认为不相关，避免生成无关测试用例
        return False


async def generate_content_summary(content: str, max_length: int = 500) -> str:
    """
    生成文档内容摘要，用于相关性检查
    """
    try:
        # 如果内容较短，直接使用
        if len(content) <= max_length:
            return content

        # 使用LLM生成摘要
        prompt_template = """
        请为以下文档内容生成简洁摘要（不超过{max_length}字），保留关键需求信息：

        {content}
        """

        prompt = PromptTemplate(
            template=prompt_template,
            input_variables=["content", "max_length"]
        )

        chain = prompt | model | StrOutputParser()
        summary = await chain.ainvoke({
            "content": content,
            "max_length": max_length
        })

        return summary

    except Exception as e:
        print(f"摘要生成失败: {str(e)}")
        # 出错时返回前500个字符
        return content[:500]


# def read_file_content(file_path: str) -> str:
#     """
#     读取不同格式的文件内容（支持 docx 和 doc 格式）
#     支持表格内容提取
#     """
#     try:
#         # 检查文件是否存在
#         if not os.path.exists(file_path):
#             raise FileNotFoundError(f"文件不存在: {file_path}")
#
#         # 获取文件扩展名
#         _, ext = os.path.splitext(file_path)
#         ext = ext.lower()
#
#         if ext == '.txt':
#             with open(file_path, 'r', encoding='utf-8') as f:
#                 return f.read()
#
#         elif ext == '.docx':
#             return read_docx(file_path)
#
#         elif ext == '.doc':
#             return read_doc(file_path)
#
#         else:
#             raise ValueError(f"不支持的文件格式: {ext}")
#
#     except Exception as e:
#         raise RuntimeError(f"读取文件内容失败: {str(e)}")
#
#
# def read_docx(file_path: str) -> str:
#     """
#     读取Word文档内容（.docx格式），支持表格
#     """
#     try:
#         # 记录文件基本信息
#         file_size = os.path.getsize(file_path)
#         print(f"开始读取 DOCX 文件: {file_path}, 大小: {file_size} 字节")
#
#         doc = Document(file_path)
#         content = []
#
#         # 处理段落
#         for para in doc.paragraphs:
#             if para.text.strip():  # 忽略空段落
#                 content.append(para.text)
#
#         # 处理表格
#         for table in doc.tables:
#             table_content = []
#             for row in table.rows:
#                 row_content = []
#                 for cell in row.cells:
#                     cell_text = cell.text.strip()
#                     if cell_text:
#                         row_content.append(cell_text)
#                 if row_content:
#                     table_content.append(" | ".join(row_content))
#
#             if table_content:
#                 content.append("\n表格内容：")
#                 content.append("\n".join(table_content))
#
#         print(f"成功读取 DOCX 文件: {file_path}, 内容长度: {len(content)}")
#         return "\n".join(content)
#
#     except Exception as e:
#         # 添加详细的错误信息
#         error_msg = f"读取DOCX文件失败: {str(e)}"
#
#         # 检查文件是否是有效的ZIP格式（DOCX本质上是ZIP文件）
#         try:
#             import zipfile
#             with zipfile.ZipFile(file_path) as z:
#                 if 'word/document.xml' not in z.namelist():
#                     error_msg += " | 文件缺少关键文件: word/document.xml"
#                 else:
#                     error_msg += " | 文件包含关键文件: word/document.xml"
#         except:
#             error_msg += " | 文件不是有效的ZIP格式"
#
#         raise RuntimeError(error_msg)
#
#
# def read_doc(file_path: str) -> str:
#     """使用 win32com 读取 .doc 文件内容（仅 Windows）"""
#     try:
#         import win32com.client as win32
#
#         # 初始化Word应用
#         word = win32.Dispatch("Word.Application")
#         word.Visible = False
#
#         # 打开文档
#         doc = word.Documents.Open(file_path)
#
#         # 获取文档内容
#         content = doc.Content.Text
#
#         # 关闭文档和应用
#         doc.Close()
#         word.Quit()
#
#         return content
#
#     except Exception as e:
#         raise RuntimeError(f"读取DOC文件失败: {str(e)}")

def read_file_content(file_path: str) -> str:
    """
    使用 LibreOffice 统一处理所有文件格式（txt, doc, docx）
    将文件转换为文本后读取内容
    """
    try:
        # 检查文件是否存在
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")

        # 获取文件扩展名
        _, ext = os.path.splitext(file_path)
        ext = ext.lower()

        # 对于 txt 文件，直接读取
        if ext == '.txt':
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    return f.read()
            except UnicodeDecodeError:
                # 尝试其他编码
                with open(file_path, 'r', encoding='latin-1') as f:
                    return f.read()

        # 使用 LibreOffice 处理其他格式
        return read_with_libreoffice(file_path)

    except Exception as e:
        raise RuntimeError(f"读取文件内容失败: {str(e)}")


def read_with_libreoffice(file_path: str) -> str:
    """
    使用 LibreOffice 将文件转换为文本
    支持 doc, docx, odt 等格式
    """
    try:
        # 创建临时目录
        temp_dir = tempfile.mkdtemp()

        # 根据操作系统设置soffice路径
        if os.name == 'nt':  # Windows
            soffice_path = r"C:\Program Files\LibreOffice\program\soffice.exe"
        elif os.name == 'posix':  # Linux or macOS
            soffice_path = "/usr/bin/soffice"  # 可能需要根据实际情况调整
        else:
            raise RuntimeError("不支持的操作系统")

        # 检查soffice是否存在
        if not os.path.exists(soffice_path):
            raise FileNotFoundError(f"未找到LibreOffice执行文件: {soffice_path}")
        # 构建转换命令
        cmd = [
            soffice_path,
            "--headless",
            "--convert-to", "txt:Text",
            "--outdir", temp_dir,
            file_path
        ]

        # 打印命令以便调试
        print(f"执行命令: {' '.join(cmd)}")

        # 运行转换命令
        result = subprocess.run(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            timeout=120  # 120秒超时
        )

        # 检查命令执行结果
        if result.returncode != 0:
            error_msg = f"LibreOffice转换失败 (返回码: {result.returncode})"
            if result.stderr:
                error_msg += f"\n错误信息: {result.stderr}"
            if result.stdout:
                error_msg += f"\n输出信息: {result.stdout}"
            raise RuntimeError(error_msg)

        # 获取转换后的文件名
        base_name = os.path.splitext(os.path.basename(file_path))[0]
        converted_path = os.path.join(temp_dir, f"{base_name}.txt")

        # 等待文件生成（LibreOffice 有时需要时间）
        for _ in range(10):  # 最多等待10秒
            if os.path.exists(converted_path):
                break
            time.sleep(1)

        # 检查转换后的文件
        if not os.path.exists(converted_path):
            # 尝试查找其他可能的文件名
            for file in os.listdir(temp_dir):
                if file.endswith('.txt'):
                    converted_path = os.path.join(temp_dir, file)
                    break
            else:
                raise FileNotFoundError(f"转换后的文件未找到: {converted_path}")

        # 读取转换后的文本内容
        try:
            with open(converted_path, 'r', encoding='utf-8') as f:
                content = f.read()
        except UnicodeDecodeError:
            with open(converted_path, 'r', encoding='latin-1') as f:
                content = f.read()

        # 清理临时文件
        try:
            os.remove(converted_path)
            os.rmdir(temp_dir)
        except:
            pass

        return content

    except Exception as e:
        # 尝试清理临时文件
        try:
            if 'converted_path' in locals() and os.path.exists(converted_path):
                os.remove(converted_path)
            if 'temp_dir' in locals() and os.path.exists(temp_dir):
                for file in os.listdir(temp_dir):
                    os.remove(os.path.join(temp_dir, file))
                os.rmdir(temp_dir)
        except:
            pass

        raise RuntimeError(f"LibreOffice处理失败: {str(e)}")


def error_response(message: str) -> Dict[str, Any]:
    """返回错误响应"""
    return {
        "code": 20000,
        "flag": False,
        "data": None,
        "msg": message
    }


def parse_and_fix_json(raw_output: str) -> Dict[str, Any]:
    """尝试修复并解析非标准JSON输出"""
    try:
        # 提取可能的JSON部分
        start = raw_output.find('{')
        end = raw_output.rfind('}') + 1
        json_str = raw_output[start:end]
        return json.loads(json_str)
    except:
        return error_response("无法解析模型输出为JSON")


async def generate_ai_title(userInput: str) -> str:
    prompt = """
    **上下文**：  
    您是一名专业的信息架构师，负责为AI对话生成高质量标题。标题将用于平台推荐系统和用户历史记录展示。

    **任务要求**：  
    1. **语义提炼**：  
       - 提取用户输入的核心意图，而非直接复制  
       - 识别关键业务领域和技术术语  
    2. **专业规范**：  
       - 使用行业标准术语（如"需求分析"而非"帮我做"）  
       - 采用"领域+动作+对象"结构（示例：电商平台测试用例设计）  
    3. **格式规范**：  
       - 长度：8-15个汉字  
       - 避免口语化表达（如"帮我"、"想要"）  
       - 不使用引号包裹  
    4. **优化方向**：  
       - 技术类对话：突出技术栈和问题类型  
       - 业务类对话：明确业务领域和场景  
       - 咨询类对话：提炼核心诉求  

    **输入内容**：  
    {userInput}

    **生成步骤**：  
    1. 识别对话领域（测试/开发/设计/咨询）  
    2. 提取核心关键词  
    3. 按专业格式重构标题  
    4. 移除所有口语化表达  
    """
    # 构建专业提示模板
    prompt_template = ChatPromptTemplate.from_messages([
        ("system", "您是企业级对话系统标题生成专家"),
        ("human", prompt),
        ("human", "请基于以上规则生成专业标题，不要包含任何引导词或说明")
    ])

    message = prompt_template.format_messages(userInput=userInput)
    try:
        response = model.invoke(message)
        title = response.content

        # 专业清洗流程
        title = title.strip('"')  # 移除引号
        title = re.sub(r'^[“”]|[“”]$', '', title)  # 移除中文引号
        title = re.sub(r'^(标题|名称|结果)：?\s*', '', title)  # 移除引导词

        # 长度规范化
        if len(title) > 20:
            title = title[:18] + ".."

        # 专业术语替换表
        term_map = {
            "帮我": "",
            "想要": "需求分析",
            "怎么做": "实施方案",
            "生成": "设计",
            "做一下": "专业开发"
        }
        for k, v in term_map.items():
            title = title.replace(k, v)

        # 确保首字母大写
        if title and not title[0].isupper():
            title = title[0].upper() + title[1:]

        return title
    except Exception as e:
        logging.error(f"标题生成失败: {e}")
